package com.ruoyi.dingtalk.util;


import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.*;
import com.dingtalk.api.response.*;
import com.ruoyi.common.exception.ServiceException;
import com.taobao.api.ApiException;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

/**
 * 钉钉工具类
 *
 * @author xueyu
 */
@Slf4j
public class DingTalkUtil {

    /**
     * 调用钉钉api获取accessToken
     *
     * @param ddAppKey    钉钉AppKey
     * @param ddAppSecret 钉钉App密钥
     * @return 钉钉响应信息 {@link com.dingtalk.api.response.OapiGettokenResponse}
     * @throws ApiException Api异常信息
     */
    public static OapiGettokenResponse getAccessTokenByApi(String ddAppKey, String ddAppSecret) throws ApiException {
        DefaultDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
        OapiGettokenRequest request = new OapiGettokenRequest();
        request.setAppkey(ddAppKey);
        request.setAppsecret(ddAppSecret);
        request.setHttpMethod("GET");
        return client.execute(request);
    }


    /**
     * 获取部门的详情。
     *
     * @param deptId      部门ID
     * @param accessToken 用户授权码
     * @return OapiDepartmentGetResponse
     */
    public static OapiDepartmentGetResponse getDeptByDeptId(String deptId, String accessToken) {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/department/get");
        OapiDepartmentGetRequest request = new OapiDepartmentGetRequest();
        request.setId(deptId);
        request.setHttpMethod("GET");
        try {
            return client.execute(request, accessToken);
        } catch (ApiException e) {
            log.error("error:", e);
            return null;
        }

    }

    /**
     * 获取部门详情 V2.0
     *
     * @param deptId      钉钉部门id
     * @param accessToken 用户授权码
     * @return OapiV2DepartmentGetResponse
     * @throws ApiException apiException
     */
    public static OapiV2DepartmentGetResponse getDeptByDeptIdV2(String deptId, String accessToken) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/department/get");
        OapiV2DepartmentGetRequest req = new OapiV2DepartmentGetRequest();
        req.setDeptId(Long.parseLong(deptId));
        req.setLanguage("zh_CN");
        req.setHttpMethod("POST");
        OapiV2DepartmentGetResponse rsp = client.execute(req, accessToken);
        log.info("获取成功，code:{},msg:{}", rsp.getCode(), rsp.getMsg());
        return rsp;
    }


    /**
     * 获部门下所有用户。
     *
     * @param ddDeptId    部门ID
     * @param accessToken 用户授权码
     * @return list
     * @throws ApiException exception
     */
    public static List<OapiUserSimplelistResponse.Userlist> listUsersByDeptId(String ddDeptId, String accessToken) throws ApiException {
        Long deptIdLong = Long.parseLong(ddDeptId);
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/simplelist");
        OapiUserSimplelistRequest request = new OapiUserSimplelistRequest();
        Long offset = 0L;
        request.setDepartmentId(deptIdLong);
        request.setSize(100L);
        request.setHttpMethod("GET");
        OapiUserSimplelistResponse response;
        List<OapiUserSimplelistResponse.Userlist> userList = new ArrayList<>();
        boolean hasMore = true;
        //hasMore代表有下页用户 增加页码获取全部
        while (hasMore) {
            request.setOffset(offset * request.getSize());
            response = client.execute(request, accessToken);
            if (response.getErrcode() != 0) {
                throw new ApiException(response.getErrmsg());
            }
            hasMore = response.getHasMore();
            userList.addAll(response.getUserlist());
            offset++;
        }

        return userList;
    }

    /**
     * 获部门下所有用户。  V2.0
     *
     * @param ddDeptId    部门ID
     * @param accessToken 用户授权码
     * @return list
     * @throws ApiException exception
     */
    public static List<OapiV2UserListResponse.ListUserResponse> listUsersByDeptIdV2(String ddDeptId, String accessToken) throws ApiException {
        Long cursor = 0L;
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/list");
        OapiV2UserListRequest req = new OapiV2UserListRequest();
        req.setDeptId(Long.parseLong(ddDeptId));
        req.setSize(100L);
        req.setOrderField("modify_desc");
        req.setContainAccessLimit(false);
        req.setLanguage("zh_CN");
        req.setHttpMethod("POST");
        List<OapiV2UserListResponse.ListUserResponse> listUserResponses = new ArrayList<>();
        boolean hasMore = true;
        while (hasMore) {
            req.setCursor(cursor);
            OapiV2UserListResponse rsp = client.execute(req, accessToken);
            if (rsp.isSuccess()) {
                OapiV2UserListResponse.PageResult result = rsp.getResult();
                hasMore = result.getHasMore();
                listUserResponses.addAll(result.getList());
                cursor = result.getNextCursor();
            } else {
                hasMore = false;
                log.error(rsp.getErrmsg());
            }
        }
        return listUserResponses;
    }

    /**
     * 获取钉钉用户信息  通过userid
     *
     * @param ddUserId    dd
     * @param accessToken 用户授权码
     * @return OapiUserGetResponse
     */
    public static OapiUserGetResponse getUserByUserId(String ddUserId, String accessToken) {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/get");
        OapiUserGetRequest request = new OapiUserGetRequest();
        request.setUserid(ddUserId);
        request.setHttpMethod("GET");
        OapiUserGetResponse oapiUserGetResponse;
        try {
            oapiUserGetResponse = client.execute(request, accessToken);
        } catch (ApiException apiException) {
            log.error("获取钉钉用户信息出现异常: ", apiException);
            throw new RuntimeException(apiException);
        }
        if (oapiUserGetResponse.isSuccess()) {
            return oapiUserGetResponse;
        } else {
            log.error("{}", oapiUserGetResponse.getErrmsg());
            throw new ServiceException("获取钉钉用户信息出错!");
        }
    }


    /**
     * 根据userId 获取用户详情 2.0
     *
     * @param ddUserId    钉钉userId
     * @param accessToken 用户授权码
     * @throws ApiException apiException
     */
    public static OapiV2UserGetResponse getUserByUserIdV2(String ddUserId, String accessToken) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");
        OapiV2UserGetRequest req = new OapiV2UserGetRequest();
        req.setUserid(ddUserId);
        req.setLanguage("zh_CN");
        req.setHttpMethod("POST");
        OapiV2UserGetResponse rsp = client.execute(req, accessToken);
        log.info("获取成功，code:{},msg:{}", rsp.getCode(), rsp.getMsg());
        return rsp;
    }


    /**
     * 获取子部门
     *
     * @param ddDeptId    父部门id
     * @param accessToken 用户授权码
     * @return list
     */
    public static List<OapiDepartmentListResponse.Department> listDeptsByDeptId(String ddDeptId, String accessToken) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/department/list");
        OapiDepartmentListRequest request = new OapiDepartmentListRequest();
        request.setId(ddDeptId);
        request.setHttpMethod("GET");
        request.setFetchChild(false);
        OapiDepartmentListResponse response = client.execute(request, accessToken);
        if (response.getErrcode() != 0) {
            throw new ApiException(response.getErrmsg());
        }
        return response.getDepartment();
    }


    /**
     * 获取子部门 v2.0
     *
     * @param ddDeptId    钉钉部门id
     * @param accessToken 用户授权码
     * @return list
     * @throws ApiException apiException
     */
    public static List<OapiV2DepartmentListsubResponse.DeptBaseResponse> listDeptsByDeptIdV2(String ddDeptId, String accessToken) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/department/listsub");
        OapiV2DepartmentListsubRequest req = new OapiV2DepartmentListsubRequest();
        req.setDeptId(Long.parseLong(ddDeptId));
        req.setLanguage("zh_CN");
        req.setHttpMethod("POST");
        OapiV2DepartmentListsubResponse rsp = client.execute(req, accessToken);
        if (!rsp.isSuccess()) {
            throw new ApiException(rsp.getErrmsg());
        }
        return rsp.getResult();
    }

    /**
     * 根据code  获取钉钉userId
     *
     * @param requestAuthCode code
     * @param accessToken     用户授权码
     * @return String   钉钉用户userId
     * @throws ApiException ApiException
     */
    public static String getUserIdByAuthCode(String requestAuthCode, String accessToken) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/getuserinfo");
        OapiUserGetuserinfoRequest req = new OapiUserGetuserinfoRequest();
        req.setCode(requestAuthCode);
        req.setHttpMethod("GET");
        OapiUserGetuserinfoResponse rsp = client.execute(req, accessToken);
        if (!rsp.isSuccess()) {
            throw new ApiException(rsp.getErrmsg());
        }
        return rsp.getUserid();
    }


    /**
     * 获取钉钉工作流实例id列表
     *
     * @param processCode 表单模板code
     * @param startDate   开始时间
     * @param endDate     结束时间
     * @param cursor      游标
     * @param ddUserIds   dd用户userId集合
     * @param accessToken 用户授权码
     * @return list
     */
    public static List<String> getProcessinstanceListids(String processCode, Date startDate, Date endDate, Long cursor, List<String> ddUserIds, String accessToken) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/processinstance/listids");
        List<String> stringList = new ArrayList<>();
        try {
            OapiProcessinstanceListidsRequest req = new OapiProcessinstanceListidsRequest();
            req.setProcessCode(processCode);
            req.setStartTime(startDate.getTime());
            req.setEndTime(endDate.getTime());
            req.setSize(20L);
            req.setHttpMethod("POST");
            StringJoiner ddUserIdStrJoiner = new StringJoiner(",");
            for (String ddUserId : ddUserIds) {
                ddUserIdStrJoiner.add(ddUserId);
            }
            req.setUseridList(ddUserIdStrJoiner.toString());
            boolean hasMore = true;
            Long hasMoreTotal = 0L;
            while (hasMore) {
                hasMoreTotal++;
                log.info("第{}次,获取钉钉审批单据列表数据,所在游标:{}", hasMoreTotal, cursor);
                req.setCursor(cursor);
                OapiProcessinstanceListidsResponse rsp = client.execute(req, accessToken);
                if (rsp.isSuccess()) {
                    OapiProcessinstanceListidsResponse.PageResult pageResult = rsp.getResult();
                    cursor = pageResult.getNextCursor();
                    if (Objects.isNull(cursor) || cursor <= 0) {
                        hasMore = false;
                    }
                    stringList.addAll(pageResult.getList());
                } else {
                    hasMore = false;
                    log.error("遍历获取钉钉的审批单据列表数据出错:{}", rsp.getErrmsg());
                }
            }
            log.info("此次遍历获取钉钉审批单据列表次数:{}", hasMoreTotal);
        } catch (ApiException e) {
            log.info("error", e);
            throw e;
        }
        return stringList;
    }


    /**
     * 获取钉钉智能工作流的审批实例信息
     *
     * @param processInstanceId 钉钉工作流 实例id
     * @param accessToken       用户授权码
     * @return OapiProcessinstanceGetResponse
     */
    public static OapiProcessinstanceGetResponse getProcessinstance(String processInstanceId, String accessToken) {
        try {
            DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/processinstance/get");
            OapiProcessinstanceGetRequest req = new OapiProcessinstanceGetRequest();
            req.setProcessInstanceId(processInstanceId);
            req.setHttpMethod("POST");
            OapiProcessinstanceGetResponse rsp = client.execute(req, accessToken);
            if (rsp.isSuccess()) {
                return rsp;
            } else {
                log.error("error: {}", rsp.getErrmsg());
            }
        } catch (ApiException e) {
            log.info("error", e);
        }
        return null;
    }


    /**
     * 发送钉钉工作通知
     *
     * @param agentId     发送消息时使用的微应用的AgentID
     * @param userIds     接收者的userid列表，最大用户列表长度100。
     * @param deptIds     接收者的部门id列表，最大列表长度20  接收者是部门ID时，包括子部门下的所有用户
     * @param toAllUser   是否发送给企业全部用户
     * @param msg         消息内容，最长不超过2048个字节
     * @param accessToken 用户授权码
     * @return OapiMessageCorpconversationAsyncsendV2Response
     * @throws ApiException Api调用异常
     */
    public static OapiMessageCorpconversationAsyncsendV2Response sendWorkMessageV2(Long agentId, List<String> userIds, List<String> deptIds,
                                                                                   boolean toAllUser, String msg, String accessToken) throws ApiException {
        if (!toAllUser) {
            if ((Objects.isNull(userIds) || userIds.isEmpty())
                    && (Objects.isNull(deptIds) || deptIds.isEmpty())) {
                throw new ApiException("UserId或者DeptId不能为空!");
            }
        }
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2");
        OapiMessageCorpconversationAsyncsendV2Request request = new OapiMessageCorpconversationAsyncsendV2Request();
        request.setAgentId(agentId);
        //用户
        StringJoiner userIdsJoiner = new StringJoiner(StringPool.COMMA);
        userIds.parallelStream().forEach(userIdsJoiner::add);
        request.setUseridList(userIdsJoiner.toString());
        //接收者的部门id列表，最大列表长度20。
        //接收者是部门ID时，包括子部门下的所有用户。
        if (Objects.nonNull(deptIds) && !deptIds.isEmpty()) {
            if (deptIds.size() > 20) {
                deptIds = deptIds.subList(0, 20);
            }
            StringJoiner deptIdJoiner = new StringJoiner(StringPool.COMMA);
            deptIds.parallelStream().forEach(deptIdJoiner::add);
            request.setDeptIdList(deptIdJoiner.toString());
        }
        //是否发送给企业全部用户。  当设置为false时必须指定userid_list或dept_id_list其中一个参数的值。
        request.setToAllUser(toAllUser);
        //消息内容，最长不超过2048个字节
        request.setMsg(msg);
        //请求为post
        request.setHttpMethod("POST");
        return client.execute(request, accessToken);
    }


    /**
     * 撤回工作通知消息
     *
     * @param agentId     发送消息时使用的微应用的ID
     * @param msgTaskId   发送消息时钉钉返回的任务ID。
     * @param accessToken 用户授权码
     * @return OapiMessageCorpconversationRecallResponse
     * @throws ApiException Api调用异常
     */
    public static OapiMessageCorpconversationRecallResponse recallWorkMessage(long agentId, long msgTaskId, String accessToken) throws ApiException {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/message/corpconversation/recall");
        OapiMessageCorpconversationRecallRequest req = new OapiMessageCorpconversationRecallRequest();
        req.setAgentId(agentId);
        req.setMsgTaskId(msgTaskId);
        req.setHttpMethod("POST");
        return client.execute(req, accessToken);
    }

}
